﻿using System;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using System.Linq;
using Image = System.Windows.Controls.Image;

namespace KeyboardImageViewer
{
    // Based on code from http://www.solidrockstable.com/blogs/PragmaticTSQL/Lists/Posts/Post.aspx?ID=37
    public class GifImageControl : Image
    {
        public static readonly DependencyProperty AllowClickToPauseProperty =
            DependencyProperty.Register("AllowClickToPause", typeof(bool), typeof(GifImageControl),
                                        new UIPropertyMetadata(true));

        public static readonly DependencyProperty GIFSourceProperty =
            DependencyProperty.Register("GIFSource", typeof(string), typeof(GifImageControl),
                                        new UIPropertyMetadata("", GIFSource_Changed));

        public static readonly DependencyProperty PlayAnimationProperty =
            DependencyProperty.Register("PlayAnimation", typeof(bool), typeof(GifImageControl),
                                        new UIPropertyMetadata(true, PlayAnimation_Changed));

        private Bitmap bitmap;

        private bool mouseClickStarted;

        public GifImageControl()
        {
            this.MouseLeftButtonDown += this.GIFImageControl_MouseLeftButtonDown;
            this.MouseLeftButtonUp += this.GIFImageControl_MouseLeftButtonUp;
            this.MouseLeave += this.GIFImageControl_MouseLeave;
            this.Click += this.GIFImageControl_Click;
        }

        public bool AllowClickToPause
        {
            get { return (bool)GetValue(AllowClickToPauseProperty); }
            set { SetValue(AllowClickToPauseProperty, value); }
        }

        public bool PlayAnimation
        {
            get { return (bool)GetValue(PlayAnimationProperty); }
            set { SetValue(PlayAnimationProperty, value); }
        }

        public string GIFSource
        {
            get { return (string)GetValue(GIFSourceProperty); }
            set { SetValue(GIFSourceProperty, value); }
        }

        private void GIFImageControl_Click(object sender, RoutedEventArgs e)
        {
            if (this.AllowClickToPause)
            {
                this.PlayAnimation = !this.PlayAnimation;
            }
        }

        private void GIFImageControl_MouseLeave(object sender, MouseEventArgs e)
        {
            this.mouseClickStarted = false;
        }

        private void GIFImageControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (this.mouseClickStarted)
            {
                this.FireClickEvent(sender, e);
            }

            this.mouseClickStarted = false;
        }

        private void GIFImageControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.mouseClickStarted = true;
        }

        private void FireClickEvent(object sender, RoutedEventArgs e)
        {
            if (null != Click)
            {
                this.Click(sender, e);
            }
        }

        public event RoutedEventHandler Click;

        private static void PlayAnimation_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var gic = (GifImageControl)d;
            if ((bool)e.NewValue)
            {
                //StartAnimation if GIFSource is properly set
                if (null != gic.bitmap)
                {
                    ImageAnimator.Animate(gic.bitmap, gic.OnFrameChanged);
                }
            }
            else
            {
                //Pause Animation
                ImageAnimator.StopAnimate(gic.bitmap, gic.OnFrameChanged);
            }
        }


        private void SetImageGIFSource()
        {
            if (this.bitmap != null)
            {
                ImageAnimator.StopAnimate(bitmap, OnFrameChanged);
                this.bitmap.Dispose();
                this.bitmap = null;
            }

            if (String.IsNullOrEmpty(this.GIFSource))
            {
                //Turn off if GIF set to null or empty
                this.Source = null;
                this.InvalidateVisual();
                return;
            }

            if (File.Exists(this.GIFSource))
            {
                bitmap = (Bitmap)System.Drawing.Image.FromFile(GIFSource);
            }
            else
            {
                //Support looking for embedded resources
                Assembly assemblyToSearch = Assembly.GetAssembly(GetType());
                bitmap = GetBitmapResourceFromAssembly(assemblyToSearch);
                if (null == bitmap)
                {
                    assemblyToSearch = Assembly.GetCallingAssembly();
                    bitmap = GetBitmapResourceFromAssembly(assemblyToSearch);
                    if (null == bitmap)
                    {
                        assemblyToSearch = Assembly.GetEntryAssembly();
                        bitmap = GetBitmapResourceFromAssembly(assemblyToSearch);
                        if (null == bitmap)
                        {
                            throw new FileNotFoundException("GIF Source was not found.", GIFSource);
                        }
                    }
                }
            }

            if (this.PlayAnimation)
            {
                ImageAnimator.Animate(bitmap, OnFrameChanged);
            }
        }

        private Bitmap GetBitmapResourceFromAssembly(Assembly assemblyToSearch)
        {
            string[] resourselist = assemblyToSearch.GetManifestResourceNames();
            if (null != assemblyToSearch.FullName)
            {
                string searchName = String.Format("{0}.{1}", assemblyToSearch.FullName.Split(',')[0], GIFSource);
                if (resourselist.Contains(searchName))
                {
                    Stream bitmapStream = assemblyToSearch.GetManifestResourceStream(searchName);
                    if (null != bitmapStream)
                        return (Bitmap)System.Drawing.Image.FromStream(bitmapStream);
                }
            }
            return null;
        }

        private static void GIFSource_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((GifImageControl)d).SetImageGIFSource();
        }


        private void OnFrameChanged(object sender, EventArgs e)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                                   new OnFrameChangedDelegate(OnFrameChangedInMainThread));
        }

        private void OnFrameChangedInMainThread()
        {
            if (PlayAnimation && this.bitmap != null)
            {
                ImageAnimator.UpdateFrames(bitmap);
                Source = GetBitmapSource(bitmap);
                InvalidateVisual();
            }
        }


        [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
        public static extern IntPtr DeleteObject(IntPtr hDc);

        private static BitmapSource GetBitmapSource(Bitmap gdiBitmap)
        {
            IntPtr hBitmap = gdiBitmap.GetHbitmap();
            BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(
                hBitmap,
                IntPtr.Zero,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());
            DeleteObject(hBitmap);
            return bitmapSource;
        }

        #region Nested type: OnFrameChangedDelegate

        private delegate void OnFrameChangedDelegate();

        #endregion
    }
}